﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Util
{
    /// <summary>
    /// Keeps track of execution times. This class provides methods that can be used for timing processes. The process to be
    /// timed should be bracketed by calls to timer.start() and timer.stop().  Repeated operations can be timed more than
    /// once. The timer will report the minimum, maximum, average and last time executed for all start/stop pairs when the
    /// timer.dump is called.
    /// 
    /// Timer instances can be obtained from a global cache implemented in {@code TimerPool}.
    ///
    /// see TimerPool
    /// </summary>
    public class Timer
    {
        private static String timeFormatter = "{###0.0000}";

        private String name;

        private double sum;
        private long count;
        private double startTime;
        private double curTime;
        private double minTime = double.MaxValue;
        private double maxTime;
        private Boolean notReliable; // if true, timing is not reliable

        /// <summary>
        /// Creates a timer.
        /// </summary>
        /// <param name="name"></param>
        public Timer(String name) 
        {
            Trace.Assert( name != null , "timers must have a name!");
            this.name = name;
            reset();
        }

        /// <summary>
        /// Retrieves the name of the timer
        /// </summary>
        /// <returns></returns>
        public String getName() 
        {
            return name;
        }


        /// <summary>
        /// Resets the timer as if it has never run before.
        /// </summary>
        public void reset() 
        {
            startTime = 0L;
            count = 0L;
            sum = 0L;
            minTime = double.MaxValue;
            maxTime = 0L;
            notReliable = false;
        }

        /// <summary>
        /// Returns true if the timer has started.
        /// </summary>
        /// <returns></returns>
        public Boolean isStarted() 
        {
            return (startTime > 0L);
        }

        /// <summary>
        /// Starts the timer running. 
        /// </summary>
        public void start() 
        {
            if (startTime != 0L) 
            {
                notReliable = true; // start called while timer already running
                Trace.WriteLine(getName() + " timer.start() called without a stop()");
            }
            startTime = (DateTime.Now -DateTime.MinValue).TotalMilliseconds;
        }


        /// <summary>
        /// Starts the timer at the given time.
        /// </summary>
        /// <param name="time"></param>
        public void start(double time) 
        {
            if (startTime != 0L) 
            {
                notReliable = true; // start called while timer already running
                Trace.WriteLine(getName() + " timer.start() called without a stop()");
            }
            double currentTimeMillis = (DateTime.Now - DateTime.MinValue).TotalMilliseconds;
            if (time > currentTimeMillis) 
            {
                throw new RankException ("Start time is later than current time");
            }
            startTime = time;
        }

        public static double currentTimeMillis()
        {
            return (DateTime.Now - DateTime.MinValue).TotalMilliseconds;
        }


        /// <summary>
        /// Stops the timer.
        /// </summary>
        /// <param name="verbose">if <code>true</code>, print out details from this run; otherwise, don't print the details</param>
        /// <returns>the duration since start in milliseconds</returns>
        public double stop(Boolean verbose) 
        {
            if (startTime == 0L) 
            {
                notReliable = true;        // stop called, but start never called
                Trace.WriteLine(getName() + " timer.stop() called without a start()");
            }
            curTime = currentTimeMillis() - startTime;
            startTime = 0L;
            if (curTime > maxTime) {
                maxTime = curTime;
            }
            if (curTime < minTime) {
                minTime = curTime;
            }
            count++;
            sum += curTime;
            if (verbose) {
                dump();
            }
            return curTime;
        }


        /// <summary>
        /// stop the timer
        /// </summary>
        public void stop() 
        {
            stop(false);
        }

        /// <summary>
        /// Dump the timer. Shows the timer details.
        /// </summary>
        public void dump() 
        {
            showTimesShort();
        }


        /**
        /// Gets the count of starts for this timer
         *
        /// @return the count
         */
        public long getCount() 
        {
            return count;
        }


        /**
        /// Returns the latest time gathered
         *
        /// @return the time in milliseconds
         */
        public double getCurTime() 
        {
            return curTime;
        }


        /**
        /// Gets the average time for this timer in milliseconds
         *
        /// @return the average time
         */
        public double getAverageTime() 
        {
            if (count == 0) 
            {
                return 0.0;
            }
            return sum / count;
        }


        /**
        /// Gets the min time for this timer in milliseconds
         *
        /// @return the min time
         */
        public double getMinTime() 
        {
            return minTime;
        }


        /**
        /// Gets the max time for this timer in milliseconds
         *
        /// @return the max time in milliseconds
         */
        public double getMaxTime() 
        {
            return maxTime;
        }



        /**
        /// Formats times into a standard format.
         *
        /// @param time the time (in seconds) to be formatted
        /// @return a string representation of the time.
         */
        private String fmtTime(double time) {
            return String.Format(timeFormatter, time);
        }


        /** Shows brief timing statistics . */
        private void showTimesShort() 
        {
            double avgTime = 0.0;

            if (count == 0) {
                return;
            }

            if (count > 0) {
                avgTime = sum / count / 1000.0;
            }

            if (notReliable) 
            {
                Trace.WriteLine(name);
                Trace.WriteLine("Not reliable.");
            } 
            else 
            {
                Trace.WriteLine(name);
                Trace.WriteLine(count);
                Trace.WriteLine(fmtTime(curTime));
                Trace.WriteLine(fmtTime(minTime));
                Trace.WriteLine(fmtTime(maxTime));
                Trace.WriteLine(fmtTime(avgTime));
                Trace.WriteLine(fmtTime(sum / 1000.0));
                Trace.WriteLine(String.Empty);
            }
        }
    }
}
